package com.listeningframework.boot.autoconfigure.security.overrides;

import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
import org.springframework.jdbc.datasource.init.DatabasePopulator;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.UserDetailsManagerConfigurer;
import org.springframework.util.Assert;

import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;

/**
 * Configures an
 * {@link org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder}
 * to have JDBC authentication. It also allows easily adding users to the
 * database used for authentication and setting up the schema.
 *
 * <p>
 * The only required method is the {@link #dataSource(javax.sql.DataSource)} all
 * other methods have reasonable defaults.
 * </p>
 *
 * @param <B>
 *            the type of the {@link ProviderManagerBuilder} that is being
 *            configured
 *
 * @author Rob Winch
 * @since 3.2
 */
public class CustomJdbcUserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>>
		extends UserDetailsManagerConfigurer<B, CustomJdbcUserDetailsManagerConfigurer<B>> {
	/**
	 * Default Schema Location
	 */
	private static final String DEFAULT_SCHEMA_LOCATION = "META-INF/security/";
	/**
	 * DDL Location Prefix
	 */
	private String defaultSchemaLocation = DEFAULT_SCHEMA_LOCATION;
	/**
	 * Default Schema Name
	 */
	private String defaultSchemaName = "security_custom_schema.sql";
	/**
	 * Default Data Name
	 */
	private String defaultDataName = "security_custom_data.sql";
	/**
	 * Scripts List
	 */
	private List<Resource> initScripts = new ArrayList<Resource>();
	/**
	 * DataSource
	 */
	private DataSource dataSource;
	/**
	 * DataSource Platform
	 */
	private String datasourcePlatform;

	/**
	 * Construct
	 *
	 * @param manager CustomJdbcUserDetailsManager
	 */
	public CustomJdbcUserDetailsManagerConfigurer(CustomJdbcUserDetailsManager manager) {
		super(manager);
	}

	/**
	 * Construct
	 */
	public CustomJdbcUserDetailsManagerConfigurer() {
		this(new CustomJdbcUserDetailsManager());
	}

	public CustomJdbcUserDetailsManagerConfigurer<B> dataSource(DataSource dataSource) throws Exception {
		this.dataSource = dataSource;
		getUserDetailsService().setDataSource(dataSource);
		return this;
	}

	public CustomJdbcUserDetailsManagerConfigurer<B> rolePrefix(String rolePrefix) throws Exception {
		getUserDetailsService().setRolePrefix(rolePrefix);
		return this;
	}

	@Override
	protected void initUserDetailsService() throws Exception {
		if (!initScripts.isEmpty()) {
			getDataSourceInit().afterPropertiesSet();
		}
		super.initUserDetailsService();
	}

	/**
	 * 
	 * @see org.springframework.security.config.annotation.authentication.configurers.userdetails.UserDetailsServiceConfigurer#getUserDetailsService()
	 */
	public CustomJdbcUserDetailsManager getUserDetailsService() {
		return (CustomJdbcUserDetailsManager) super.getUserDetailsService();
	}

	public CustomJdbcUserDetailsManagerConfigurer<B> withDefaultSchema(boolean withDefaultSchema) {
		Assert.notNull(datasourcePlatform, "DataSource Platform is Needed");
		if (withDefaultSchema) {
			this.initScripts.add(getScriptLocation(defaultSchemaName));
		}
		return this;
	}

	public CustomJdbcUserDetailsManagerConfigurer<B> withDefaultData(boolean withDefaultData) {
		Assert.notNull(datasourcePlatform, "DataSource Platform is Needed");
		if (withDefaultData) {
			this.initScripts.add(getScriptLocation(defaultDataName));
		}
		return this;
	}

	public CustomJdbcUserDetailsManagerConfigurer<B> withDatasourcePlatform(String datasourcePlatform) {
		this.datasourcePlatform = datasourcePlatform;
		return this;
	}

	/**
	 * Custom The Schema Location
	 *
	 * @param defaultSchemaLocation 默认脚本存放路径
	 * @return CustomJdbcUserDetailsManagerConfigurer
	 */
	public CustomJdbcUserDetailsManagerConfigurer<B> withDefaultSchemaLocation(String defaultSchemaLocation) {
		if (StringUtils.isNotBlank(defaultSchemaLocation)) {
			this.defaultSchemaLocation = defaultSchemaLocation;
		}
		return this;
	}

	/**
	 * Custom The Schema Name
	 *
	 * @param defaultSchemaName 默认DDL脚本名称
	 * @return CustomJdbcUserDetailsManagerConfigurer
	 */
	public CustomJdbcUserDetailsManagerConfigurer<B> withDefaultSchemaName(String defaultSchemaName) {
		if (StringUtils.isNotBlank(defaultSchemaName)) {
			this.defaultSchemaName = defaultSchemaName;
		}
		return this;
	}

	public CustomJdbcUserDetailsManagerConfigurer<B> withDefaultDataName(String defaultDataName) {
		if (StringUtils.isNotBlank(defaultDataName)) {
			this.defaultDataName = defaultDataName;
		}
		return this;
	}

	/**
	 * Strategy used to populate, initialize, or clean up a database.
	 *
	 * @return DatabasePopulator
	 */
	protected DatabasePopulator getDatabasePopulator() {
		ResourceDatabasePopulator dbp = new ResourceDatabasePopulator();
		dbp.setScripts(initScripts.toArray(new Resource[initScripts.size()]));
		dbp.setContinueOnError(true);
		dbp.setSqlScriptEncoding("utf-8");
		return dbp;
	}

	private DataSourceInitializer getDataSourceInit() {
		DataSourceInitializer dsi = new DataSourceInitializer();
		dsi.setDatabasePopulator(getDatabasePopulator());
		dsi.setDataSource(dataSource);
		return dsi;
	}

	private ClassPathResource getScriptLocation(String resourcePath) {
		return new ClassPathResource(defaultSchemaLocation + resourcePath);
	}

}
